package com.example.filestore;

import com.example.domain.BaseEntity;
import com.example.persistence.PersistenceService;
import com.example.persistence.exception.PersistenceException;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * @author 01
 * @date 2019-10-05
 **/
@Slf4j
public class PersistenceServiceImpl implements PersistenceService {

    private static final Path DATA_PATH = Paths.get(".", "data");
    private static final String GLOB = "*.bin";

    @Override
    public <T extends BaseEntity> List<T> list(Class<T> type) throws PersistenceException {
        List<T> result = new ArrayList<>();
        try (DirectoryStream<Path> directoryStream =
                     Files.newDirectoryStream(getEntitiesPath(type), GLOB)) {
            for (Path path : directoryStream) {
                result.add(readEntity(path));
            }
        } catch (IOException e) {
            throw new PersistenceException(e);
        }
        return result;
    }

    @Override
    public <T extends BaseEntity> Optional<T> get(Class<T> type, Long id) throws PersistenceException {
        Path path = getEntityPath(type, id);
        BaseEntity baseEntity = readEntity(path);

        return baseEntity == null ? Optional.empty() : Optional.of(type.cast(baseEntity));
    }

    @Override
    public void save(BaseEntity entity) throws PersistenceException {
        saveEntity(entity);
    }

    @Override
    public <T extends BaseEntity> void delete(Class<T> type, Long id) throws PersistenceException {
        deleteEntity(type, id);
    }

    private Path getEntitiesPath(Class<?> type) {
        return DATA_PATH.resolve(type.getSimpleName());
    }

    private Path getEntityPath(Class<?> type, Long id) {
        return getEntitiesPath(type).resolve(String.format("%s.bin", id));
    }

    private <T extends BaseEntity> T readEntity(Path path) {
        try (ObjectInputStream inputStream = new ObjectInputStream(Files.newInputStream(path))) {
            return (T) inputStream.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new PersistenceException(e);
        }
    }

    private void saveEntity(BaseEntity entity) {
        Path path = getEntityPath(entity.getClass(), entity.getId());
        try (ObjectOutputStream outputStream = new ObjectOutputStream(Files.newOutputStream(path))) {
            outputStream.writeObject(entity);
            outputStream.flush();
        } catch (IOException e) {
            throw new PersistenceException(e);
        }
    }

    private void deleteEntity(Class<?> type, Long id) {
        try {
            Path path = getEntityPath(type, id);
            Files.deleteIfExists(path);
        } catch (IOException e) {
            throw new PersistenceException(e);
        }
    }
}
